Skip to content

fix: newsletter editor formatting and working group activity#2000

Merged
bokelley merged 10 commits intomainfrom
bokelley/newsletter-editor-fix
Apr 8, 2026
Merged

fix: newsletter editor formatting and working group activity#2000
bokelley merged 10 commits intomainfrom
bokelley/newsletter-editor-fix

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented Apr 8, 2026

Summary

  • Editor's note paste fix: Pasting rich text into the editor's note field no longer produces a blob of text. The paste handler preserves paragraph breaks and converts links to Slack format. Uses innerText instead of textContent so line breaks survive save/render.
  • Email rendering: Editor's note newlines now render as <br> in the email template.
  • From the Inside: Working group cards now show meeting recaps (linked titles, dates, summaries) and active Slack threads (linked summaries, reply/participant counts) — data that was already being collected but not displayed.
  • Activity filter: Working groups only appear if they have actual recent meetings or threads. Stale AI summaries alone no longer qualify a group for inclusion.
  • Security: Added safeHref() protocol validation on all dynamic href attributes to prevent javascript: URI injection.

Test plan

  • All 563 unit tests pass
  • TypeScript compiles clean
  • Playwright browser tests pass for paste handler, WG card rendering, and renderSlackLinks
  • Code review: no must-fix findings
  • Security review: no must-fix findings; should-fix (protocol validation) addressed

🤖 Generated with Claude Code

Editor's note was losing all formatting when pasting rich text because
the save function used textContent which strips line breaks. Now uses
innerText and a paste handler that preserves paragraph structure and
converts links to Slack format.

Working groups in "From the Inside" now show meeting recaps and active
threads instead of just a summary description. Groups without actual
recent activity (meetings or threads) are filtered out.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bokelley and others added 3 commits April 8, 2026 08:30
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CodeQL flagged the DOMParser-based HTML paste handler as a potential
XSS vector. Simplified to use text/plain from clipboard which preserves
line breaks without any HTML processing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pasting rich text now preserves links by extracting href/label pairs
from clipboard HTML and converting to Slack format. Uses text/plain as
the base (safe) and only reads link data from the parsed DOM without
modifying it, avoiding the CodeQL taint flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bokelley and others added 4 commits April 8, 2026 12:06
CodeQL flags any DOMParser usage with clipboard data as js/xss. Using
plain text/plain from clipboard is sufficient to preserve line breaks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the plain contenteditable div with a TipTap-based rich text
editor for the editor's note field. Supports bold, italic, links,
and bullet/ordered lists with a toolbar UI.

- Bundle TipTap core + starter-kit + link extension via esbuild
- Store HTML in editorsNote field (backward compat with plain text)
- Sanitize HTML server-side with DOMPurify (allowlist of safe tags)
- Email rendering adds inline styles for email client compatibility
- Slack rendering converts HTML to mrkdwn format
- Increase MAX_TEXT_LENGTH from 500 to 2000 for HTML overhead

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TipTap is only used by the build script (scripts/build-tiptap.mjs) and
the bundle is committed as server/public/vendor/tiptap.js. No need for
these deps in the Docker build. Regenerated lockfile with Node 20 to
fix pre-existing npm ci sync issue in Docker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The isHtml check was matching Slack-format links (<https://...>) as HTML
because the regex was too broad. Narrowed to only match actual HTML tags
(p, div, br, strong, em, ul, ol, li, a).

Added DOMPurify.sanitize before HTML-to-text conversions to satisfy
CodeQL's incomplete-sanitization and double-escaping checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace regex-based HTML tag stripping with DOMPurify RETURN_DOM +
recursive DOM traversal. CodeQL cannot trace safety through DOMPurify's
sanitize-then-regex pattern, but DOM traversal avoids the flagged
regex patterns entirely.

Also use TipTap's isEmpty API instead of regex for empty check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 0f78057 into main Apr 8, 2026
12 checks passed
@bokelley bokelley deleted the bokelley/newsletter-editor-fix branch April 8, 2026 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants